//
// Buffer.h
//
// Library: Foundation
// Package: Core
// Module:  Buffer
//
// Definition of the Buffer class.
//
// Copyright (c) 2012, Applied Informatics Software Engineering GmbH.
// and Contributors.
//
// SPDX-License-Identifier:	BSL-1.0
//


#ifndef Foundation_Buffer_INCLUDED
#define Foundation_Buffer_INCLUDED


#include <cstddef>
#include <cstring>
#include "Poco/Exception.h"
#include "Poco/Foundation.h"


namespace Poco
{


template <class T>
class Buffer
/// A buffer class that allocates a buffer of a given type and size
/// in the constructor and deallocates the buffer in the destructor.
///
/// This class is useful everywhere where a temporary buffer
/// is needed.
{
public:
    Buffer(std::size_t capacity) : _capacity(capacity), _used(capacity), _ptr(0), _ownMem(true)
    /// Creates and allocates the Buffer.
    {
        if (capacity > 0)
        {
            _ptr = new T[capacity];
        }
    }

    Buffer(T * pMem, std::size_t length) : _capacity(length), _used(length), _ptr(pMem), _ownMem(false)
    /// Creates the Buffer. Length argument specifies the length
    /// of the supplied memory pointed to by pMem in the number
    /// of elements of type T. Supplied pointer is considered
    /// blank and not owned by Buffer, so in this case Buffer
    /// only acts as a wrapper around externally supplied
    /// (and lifetime-managed) memory.
    {
    }

    Buffer(const T * pMem, std::size_t length) : _capacity(length), _used(length), _ptr(0), _ownMem(true)
    /// Creates and allocates the Buffer; copies the contents of
    /// the supplied memory into the buffer. Length argument specifies
    /// the length of the supplied memory pointed to by pMem in the
    /// number of elements of type T.
    {
        if (_capacity > 0)
        {
            _ptr = new T[_capacity];
            std::memcpy(_ptr, pMem, _used * sizeof(T));
        }
    }

    Buffer(const Buffer & other)
        : /// Copy constructor.
        _capacity(other._used)
        , _used(other._used)
        , _ptr(0)
        , _ownMem(true)
    {
        if (_used)
        {
            _ptr = new T[_used];
            std::memcpy(_ptr, other._ptr, _used * sizeof(T));
        }
    }

    Buffer & operator=(const Buffer & other)
    /// Assignment operator.
    {
        if (this != &other)
        {
            Buffer tmp(other);
            swap(tmp);
        }

        return *this;
    }

    ~Buffer()
    /// Destroys the Buffer.
    {
        if (_ownMem)
            delete[] _ptr;
    }

    void resize(std::size_t newCapacity, bool preserveContent = true)
    /// Resizes the buffer capacity and size. If preserveContent is true,
    /// the content of the old buffer is copied over to the
    /// new buffer. The new capacity can be larger or smaller than
    /// the current one; if it is smaller, capacity will remain intact.
    /// Size will always be set to the new capacity.
    ///
    /// Buffers only wrapping externally owned storage can not be
    /// resized. If resize is attempted on those, IllegalAccessException
    /// is thrown.
    {
        if (!_ownMem)
            throw Poco::InvalidAccessException("Cannot resize buffer which does not own its storage.");

        if (newCapacity > _capacity)
        {
            T * ptr = new T[newCapacity];
            if (preserveContent && _used > 0)
            {
                std::memcpy(ptr, _ptr, _used * sizeof(T));
            }
            delete[] _ptr;
            _ptr = ptr;
            _capacity = newCapacity;
        }

        _used = newCapacity;
    }

    void setCapacity(std::size_t newCapacity, bool preserveContent = true)
    /// Sets the buffer capacity. If preserveContent is true,
    /// the content of the old buffer is copied over to the
    /// new buffer. The new capacity can be larger or smaller than
    /// the current one; size will be set to the new capacity only if
    /// new capacity is smaller than the current size, otherwise it will
    /// remain intact.
    ///
    /// Buffers only wrapping externally owned storage can not be
    /// resized. If resize is attempted on those, IllegalAccessException
    /// is thrown.
    {
        if (!_ownMem)
            throw Poco::InvalidAccessException("Cannot resize buffer which does not own its storage.");

        if (newCapacity != _capacity)
        {
            T * ptr = 0;
            if (newCapacity > 0)
            {
                ptr = new T[newCapacity];
                if (preserveContent)
                {
                    std::size_t newSz = _used < newCapacity ? _used : newCapacity;
                    std::memcpy(ptr, _ptr, newSz * sizeof(T));
                }
            }
            delete[] _ptr;
            _ptr = ptr;
            _capacity = newCapacity;

            if (newCapacity < _used)
                _used = newCapacity;
        }
    }

    void assign(const T * buf, std::size_t sz)
    /// Assigns the argument buffer to this buffer.
    /// If necessary, resizes the buffer.
    {
        if (0 == sz)
            return;
        if (sz > _capacity)
            resize(sz, false);
        std::memcpy(_ptr, buf, sz * sizeof(T));
        _used = sz;
    }

    void append(const T * buf, std::size_t sz)
    /// Resizes this buffer and appends the argument buffer.
    {
        if (0 == sz)
            return;
        resize(_used + sz, true);
        std::memcpy(_ptr + _used - sz, buf, sz * sizeof(T));
    }

    void append(T val)
    /// Resizes this buffer by one element and appends the argument value.
    {
        resize(_used + 1, true);
        _ptr[_used - 1] = val;
    }

    void append(const Buffer & buf)
    /// Resizes this buffer and appends the argument buffer.
    {
        append(buf.begin(), buf.size());
    }

    std::size_t capacity() const
    /// Returns the allocated memory size in elements.
    {
        return _capacity;
    }

    std::size_t capacityBytes() const
    /// Returns the allocated memory size in bytes.
    {
        return _capacity * sizeof(T);
    }

    void swap(Buffer & other)
    /// Swaps the buffer with another one.
    {
        using std::swap;

        swap(_ptr, other._ptr);
        swap(_capacity, other._capacity);
        swap(_used, other._used);
    }

    bool operator==(const Buffer & other) const
    /// Compare operator.
    {
        if (this != &other)
        {
            if (_used == other._used)
            {
                if (std::memcmp(_ptr, other._ptr, _used * sizeof(T)) == 0)
                {
                    return true;
                }
            }
            return false;
        }

        return true;
    }

    bool operator!=(const Buffer & other) const
    /// Compare operator.
    {
        return !(*this == other);
    }

    void clear()
    /// Sets the contents of the buffer to zero.
    {
        std::memset(_ptr, 0, _used * sizeof(T));
    }

    std::size_t size() const
    /// Returns the used size of the buffer in elements.
    {
        return _used;
    }

    std::size_t sizeBytes() const
    /// Returns the used size of the buffer in bytes.
    {
        return _used * sizeof(T);
    }

    T * begin()
    /// Returns a pointer to the beginning of the buffer.
    {
        return _ptr;
    }

    const T * begin() const
    /// Returns a pointer to the beginning of the buffer.
    {
        return _ptr;
    }

    T * end()
    /// Returns a pointer to end of the buffer.
    {
        return _ptr + _used;
    }

    const T * end() const
    /// Returns a pointer to the end of the buffer.
    {
        return _ptr + _used;
    }

    bool empty() const
    /// Return true if buffer is empty.
    {
        return 0 == _used;
    }

    T & operator[](std::size_t index)
    {
        poco_assert(index < _used);

        return _ptr[index];
    }

    const T & operator[](std::size_t index) const
    {
        poco_assert(index < _used);

        return _ptr[index];
    }

private:
    Buffer();

    std::size_t _capacity;
    std::size_t _used;
    T * _ptr;
    bool _ownMem;
};


} // namespace Poco


#endif // Foundation_Buffer_INCLUDED
